//
// RectSelection.java
//
package com.photoexpert;

// RectSelection represents a rectangle selection area
// You can either get a screen area or image area.
//
// ScreenArea is the area on Presentor (GUI), it is INCLUSIVE
// A ScreenArea (x=0, y=0, width=100, height=100) actually represents a 101*101 image
// This is to make the selection of edges easier (i.e., can select edges of the
// original image)
//
// ImageArea is the area on Implementor (BufferedImage), it is REGULAR
//
// An ImageArea (x=0, y=0, width=100, height=100) represents a 100*100 image
//

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.RescaleOp;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

class RectSelection {

    RectSelection() {
        _presentor = Presentor.Instance();
        _zoomRate = _presentor.getZoomRate();
        // create Thread that draws the selection border
        final Runnable redrawer = new Runnable() {
            @Override
            public void run() {
                _presentor.repaint(0, _screenArea._x, _screenArea._y,
                    _screenArea._width + 1, _screenArea._height + 1);
            }
        };
        // the RectSelection creates a thread to draw itself, when startDrawFrame is called.
        // the thread will stop and delete itself when selection is no longer valid.
        _drawer = new Runnable() {
            @Override
            public void run() {
                while(_screenArea._width > 0 && _screenArea._height > 0) {
                    try {
                        if(++_drawStart >= _drawStep * 2)
                            _drawStart = 0;
                        EventQueue.invokeLater(redrawer);
                        Thread.sleep(100);
                    }
                    catch (InterruptedException e) {
                        break;
                    }
                }
                _thread = null;
            }
        };
    }

    Area getScreenArea() { return _screenArea; }

    Area getImageArea() {
        Area area = new Area();
        area._x = (int)(_screenArea._x / _zoomRate);
        area._y = (int)(_screenArea._y / _zoomRate);
        area._width = (int)(_screenArea._width / _zoomRate) + 1;
        area._height = (int)(_screenArea._height / _zoomRate) + 1;
        return area;
    }

    void setPoint(int x, int y) {
        _screenArea._x = x;
        _screenArea._y = y;
    }

    void setDimension(int width, int height) {
        _screenArea._width = width;
        _screenArea._height = height;
        if(width > 0 && height > 0)
            App.Instance().selectionBecomeValid(true);
    }

    void destroy() {
        _logger.log(Level.INFO, "Selection destroyed");
        int x = _screenArea._x;
        int y = _screenArea._y;
        int width = _screenArea._width;
        int height = _screenArea._height;
        _screenArea._x = 0;
        _screenArea._y = 0;
        _screenArea._width = 0;
        _screenArea._height = 0;
        _presentor.repaint(0, x, y, width + 1, height + 1);
        App.Instance().selectionBecomeValid(false);
    }

    void zoomRateChanged() {
        double oldRate = _zoomRate;
        _zoomRate = _presentor.getZoomRate();
        _screenArea._x = (int)(_screenArea._x / oldRate * _zoomRate);
        _screenArea._y = (int)(_screenArea._y / oldRate * _zoomRate);
        _screenArea._width = (int)(_screenArea._width / oldRate * _zoomRate);
        _screenArea._height = (int)(_screenArea._height / oldRate * _zoomRate);
    }

    void startDrawBorder() {
        if(_thread == null) {
            _thread = new Thread(_drawer);
            _thread.start();
        }
    }

    void drawBorder(Graphics g) {
        g.setColor(Color.black);
        int p1 = _screenArea._x + _drawStart;
        drawHBorder(g, p1);
        p1 = _screenArea._y + _drawStart;
        drawVBorder(g, p1);
        g.setColor(Color.white);
        if(_drawStart < _drawStep)
            p1 = _screenArea._x + _drawStart + _drawStep;
        else
            p1 = _screenArea._x + _drawStart - _drawStep;
        drawHBorder(g, p1);
        if(_drawStart < _drawStep)
            p1 = _screenArea._y + _drawStart + _drawStep;
        else
            p1 = _screenArea._y + _drawStart - _drawStep;
        drawVBorder(g, p1);
    }

    boolean copy() {
        Area area = getImageArea();
        _logger.log(Level.INFO, "Copy rectangle selection to clipboard:{0},{1},{2},{3}",
            new Object[]{area._x, area._y, area._width, area._height});
        if(area._height <= 0 || area._height <= 0) {
            JOptionPane.showMessageDialog(App.Instance(), "Nothing selected.", "Photo Expert",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }
        BufferedImage src = Implementor.Instance().getImage().getSubimage(
            area._x, area._y, area._width, area._height);
        int type = (src.getTransparency() == Transparency.OPAQUE) ?
            BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage dst = new BufferedImage(area._width, area._height, type);
        Graphics2D g2 = dst.createGraphics();
        g2.drawImage(src, 0, 0, null);
        g2.dispose();
        ImageTransferable imageSelection = new ImageTransferable(dst);
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imageSelection, null);
        return true;
    }

    boolean delete() {
        Area area = getImageArea();
        _logger.log(Level.INFO, "Delete rectangle selection:{0},{1},{2},{3}",
            new Object[]{area._x, area._y, area._width, area._height});
        if(area._height <= 0 || area._height <= 0) {
            JOptionPane.showMessageDialog(App.Instance(), "Nothing selected.", "Photo Expert",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }
        BufferedImage img = Implementor.Instance().getImage();
        Graphics2D g = img.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(area._x, area._y, area._width, area._height);
        if(img.getTransparency() != Transparency.OPAQUE) {
            ColorModel cm = img.getColorModel();
            if(cm instanceof DirectColorModel) {
                float[] scales = new float[] { 1f, 1f, 1f, 0f };
                float[] offsets = new float[] { 0f, 0f, 0f, 0f };
                RescaleOp operator = new RescaleOp(scales, offsets, null);
                BufferedImage operand = img.getSubimage(area._x, area._y,
                     area._width, area._height);
                operator.filter(operand, operand);
            }
        }
        g.dispose();
        return true;
    }

    /* ------------------------------------------------------------------------ */

    private void drawHBorder(Graphics g, int start)
    {
        int p1 = start;
        int p2 = p1 + _drawStep - 1;
        int x2 = _screenArea._x + _screenArea._width;
        int y2 = _screenArea._y + _screenArea._height;
        if(start > _screenArea._x +  _drawStep)
        {
            g.drawLine(_screenArea._x, _screenArea._y, p1 - _drawStep - 1, _screenArea._y);
            g.drawLine(_screenArea._x, y2, p1 - _drawStep - 1, y2);
        }
        while(p2 <= x2)
        {
            g.drawLine(p1, _screenArea._y, p2, _screenArea._y);
            g.drawLine(p1, y2, p2, y2);
            p1 += _drawStep * 2;
            p2 += _drawStep * 2;
        }
        if(p1 < x2)
        {
            g.drawLine(p1, _screenArea._y, x2, _screenArea._y);
            g.drawLine(p1, y2, x2, y2);
        }
    }

    private void drawVBorder(Graphics g, int start)
    {
        int p1 = start;
        int p2 = p1 + _drawStep - 1;
        int x2 = _screenArea._x + _screenArea._width;
        int y2 = _screenArea._y + _screenArea._height;
        if(start > _screenArea._y +  _drawStep)
        {
            g.drawLine(_screenArea._x, _screenArea._y, _screenArea._x, p1 - _drawStep - 1);
            g.drawLine(x2, _screenArea._y, x2, p1 - _drawStep - 1);
        }
        while(p2 <= y2)
        {
            g.drawLine(_screenArea._x, p1, _screenArea._x, p2);
            g.drawLine(x2, p1, x2, p2);
            p1 += _drawStep * 2;
            p2 += _drawStep * 2;
        }
        if(p1 < y2)
        {
            g.drawLine(_screenArea._x, p1, _screenArea._x, y2);
            g.drawLine(x2, p1, x2, y2);
        }
    }

    static class Area {
        int                     _x = 0;
        int                     _y = 0;
        int                     _width = 0;
        int                     _height = 0;
    }

    private Area                _screenArea = new Area();
    private static final Logger _logger = App._logger;
    private Presentor           _presentor = null;
    private double              _zoomRate = 1d;
    private int                 _drawStart = 0;
    private final int           _drawStep = 5;
    private Thread              _thread = null;
    private Runnable            _drawer = null;
}
